package com.iraytek.rtsplib;

import android.util.Base64;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;


import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import pedro.rtsp.rtsp.Body;
import pedro.rtsp.rtsp.CommandsManager;
import pedro.rtsp.rtsp.Protocol;
import pedro.rtsp.utils.ConnectCheckerRtsp;

public class ServerCommandManager extends CommandsManager {
    private final String TAG;
    @NonNull
    private ArrayList<Integer> audioPorts;
    @NonNull
    private ArrayList<Integer> videoPorts;
    private Integer track;
    private String sessionId;
    private final String serverIp;
    private final int serverPort;
    @Nullable
    private final String clientIp;

    @NonNull
    public final ArrayList<Integer> getAudioPorts() {
        return this.audioPorts;
    }

    public final void setAudioPorts(@NonNull ArrayList aPorts) {
        this.audioPorts = aPorts;
    }

    @NonNull
    public final ArrayList<Integer> getVideoPorts() {
        return this.videoPorts;
    }

    public final void setVideoPorts(@NonNull ArrayList vPorts) {
        this.videoPorts = vPorts;
    }

    @NonNull
    public final String createResponse(@NonNull String action, @NonNull String request, int cSeq) {
        action = action.toLowerCase();
        if (action.contains("options")) {
            return createOptions(cSeq);
        } else if (action.contains("describe")) {
            return createDescribe(cSeq);
        } else if (action.contains("setup")) {
            Protocol protocol = getProtocol(request);
            setProtocol(protocol);
            if (protocol == Protocol.TCP) {
                getTrack(request);
                if (track != null) {
                    return createSetup(cSeq);
                }
            } else if (protocol == Protocol.UDP) {
                if (loadPorts(request)) {
                    return createSetup(cSeq);
                }
            }
            return createError(500, cSeq);
        } else if (action.contains("play")) {
            return createPlay(cSeq);
        } else if (action.contains("pause")) {
            return createPause(cSeq);
        } else if (action.contains("teardown")) {
            return createTeardown(cSeq);
        }
        return createError(400, cSeq);
    }

    private Protocol getProtocol(String request) {
        return request.toUpperCase().contains("UDP") || loadPorts(request) ? Protocol.UDP : Protocol.TCP;
    }

    private boolean loadPorts(String request) {
        ArrayList<Integer> ports = new ArrayList<>();
        Matcher portsMatcher = Pattern.compile("client_port=(\\d+)(?:-(\\d+))?", Pattern.CASE_INSENSITIVE).matcher(request);
        if (portsMatcher.find()) {
            String group = portsMatcher.group(1);
            if (group != null) {
                ports.add(Integer.parseInt(group));
            }
            group = portsMatcher.group(2);
            if (group != null) {
                ports.add(Integer.parseInt(group));
            }

            getTrack(request);
            if (track == null) {
                Log.e(this.TAG, "Track id not found");
                return false;
            }

            if (track == 0) {
                audioPorts.clear();
                audioPorts.addAll(ports);
            } else {
                videoPorts.clear();
                videoPorts.addAll(ports);
            }

            Log.i(this.TAG, "Video ports: " + this.videoPorts);
            Log.i(this.TAG, "Audio ports: " + this.audioPorts);
            return true;

        } else {
            Log.e(this.TAG, "UDP ports not found");
            return false;
        }
    }

    private final void getTrack(String request) {
        Matcher trackMatcher = Pattern.compile("trackID=(\\w+)", 2).matcher(request);
        if (trackMatcher.find()) {
            String group = trackMatcher.group(1);
            if (group != null) {
                track = Integer.parseInt(group);
            }
        } else {
            track = null;
        }
    }

    public final int getCSeq(@NonNull String request) {
        Matcher cSeqMatcher = Pattern.compile("CSeq\\s*:\\s*(\\d+)", 2).matcher(request);
        if (cSeqMatcher.find()) {
            String group = cSeqMatcher.group(1);
            if (group != null) {
                return Integer.parseInt(group);
            }
        }
        return -1;
    }

    @NonNull
    public final String getRequest(@NonNull BufferedReader input) throws IOException, IllegalStateException {
        StringBuilder request = new StringBuilder();
        String line;
        while ((line = input.readLine()) != null && line.length() > 3) {
            request.append(line).append("\n");
        }
        return request.toString();
    }

    private final String createStatus(int code) {
        String status;
        switch (code) {
            case 200:
                status = "200 OK";
                break;
            case 400:
                status = "400 Bad Request";
                break;
            case 401:
                status = "401 Unauthorized";
                break;
            case 404:
                status = "404 Not Found";
                break;
            case 500:
            default:
                status = "500 Internal Server Error";
        }

        return status;
    }

    @NonNull
    public final String createError(int code, int cSeq) {
        return "RTSP/1.0 " + this.createStatus(code) + "\r\nServer: iray Server\r\nCseq: " + cSeq + "\r\n\r\n";
    }

    private final String createHeader(int cSeq) {
        return "RTSP/1.0 " + this.createStatus(200) + "\r\nServer: iray Server\r\nCseq: " + cSeq + "\r\n";
    }

    private final String createOptions(int cSeq) {
        return this.createHeader(cSeq) + "Public: DESCRIBE,SETUP,TEARDOWN,PLAY,PAUSE\r\n\r\n";
    }

    private final String createDescribe(int cSeq) {
        String body = this.createBody();
        return this.createHeader(cSeq) + "Content-Length: " + body.length() + "\r\nContent-Base: " + this.serverIp + ':' + this.serverPort + "/\r\nContent-Type: application/sdp\r\n\r\n" + body;
    }

    private final String createBody() {
        String audioBody = Body.INSTANCE.createAacBody(this.getTrackAudio(), this.getSampleRate(), this.isStereo());
        String videoBody = getVps() == null ? Body.INSTANCE.createH264Body(getTrackVideo(), encodeToString(getSps()), encodeToString(getPps())) :
                Body.INSTANCE.createH265Body(getTrackVideo(), encodeToString(getSps()), encodeToString(getPps()), encodeToString(getVps()));

        return "v=0\r\no=- 0 0 IN IP4 " + serverIp + "\r\ns=Unnamed\r\ni=N/A\r\nc=IN IP4 " + clientIp + "\r\nt=0 0\r\na=recvonly\r\n" + audioBody + videoBody + "\r\n";
    }


    @NonNull
    public String createSetup(int cSeq) {
        String protocolSetup;
        if (this.getProtocol() == Protocol.UDP) {
            protocolSetup = "UDP;unicast;destination=" + this.clientIp + ";client_port=8000-8001;server_port=39000-35968";
        } else {
            protocolSetup = "TCP;unicast;interleaved=" + (2 * track) + "-" + (2 * track + 1);
        }
        return this.createHeader(cSeq) + "Content-Length: 0\r\nTransport: RTP/AVP/" + protocolSetup + ";mode=play\r\nSession: " + sessionId + "\r\nCache-Control: no-cache\r\n\r\n";
    }

    private final String createPlay(int cSeq) {
        return this.createHeader(cSeq) + "Content-Length: 0\r\nRTP-Info: url=rtsp://" + this.serverIp + ':' + this.serverPort + "/\r\nSession: " + sessionId + "\r\n\r\n";
    }

    private final String createPause(int cSeq) {
        return this.createHeader(cSeq) + "Content-Length: 0\r\n\r\n";
    }

    private final String createTeardown(int cSeq) {
        return this.createHeader(cSeq) + "\r\n";
    }

    private final String encodeToString(byte[] bytes) {
        return Base64.encodeToString(bytes, 0, bytes.length, 2);
    }

    @Nullable
    public final String getClientIp() {
        return this.clientIp;
    }

    public ServerCommandManager(@NonNull String serverIp, int serverPort, @Nullable String clientIp, ConnectCheckerRtsp connectCheckerRtsp) {
        super(connectCheckerRtsp);
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        this.clientIp = clientIp;
        this.TAG = "ServerCommandManager";
        this.audioPorts = new ArrayList<>();
        this.videoPorts = new ArrayList<>();
        sessionId = Long.toHexString(System.currentTimeMillis());
    }
}
